package com.baoteng.captcha.recognition.service.impl;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Random;

import javax.imageio.ImageIO;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.baoteng.captcha.recognition.vo.CaptchaVO;
import com.baoteng.captcha.recognition.vo.RecognizeResultVO;
import com.baoteng.captcha.recognition.vo.SingleCharImageBuffer;
import com.google.common.collect.Lists;
import com.google.common.io.PatternFilenameFilter;

import net.sourceforge.tess4j.ITessAPI.TessPageSegMode;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract1;

@Service("picsCaptchaService")
public class PicsCaptchaService {

	private static final Logger logger = LoggerFactory.getLogger(PicsCaptchaService.class);
	
	public static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 
	
	/**
	 * 16位随机字符串
	 * @param length
	 * @return
	 */
	public String generateString(int length) {
		String baseCode = Long.toHexString(System.nanoTime());
		if (baseCode.length() >= length) {
			return baseCode.substring(0, length);
		}
		StringBuffer sb = new StringBuffer();
		sb.append(RandomStringUtils.random(length - baseCode.length(), PicsCaptchaService.ALLCHAR.toCharArray()));
		sb.append(baseCode);
		return sb.toString();
	}
		
	private boolean isDebug = false;
	
	@Value("${tess.data.path}")
	private String tessDataPath="D:/Tesseract-OCR/tessdata";

	@Value("${pics.test.data.path:}")
	private String testPath;
	
	public boolean isDebug() {
		return isDebug;
	}

	public void setDebug(boolean isDebug) {
		this.isDebug = isDebug;
	}

	public String getTessDataPath() {
		return tessDataPath;
	}

	public void setTessDataPath(String tessDataPath) {
		this.tessDataPath = tessDataPath;
	}

	public String getTestPath() {
		return testPath;
	}

	public void setTestPath(String testPath) {
		this.testPath = testPath;
	}

	public CaptchaVO test() throws Exception{
		if(!StringUtils.isNotBlank(testPath)){
			return null;
		}
		File dataPath = new File(testPath);
		if(!dataPath.exists()){
			throw new Exception("test data directory don`t exist.");
		}
		String[] fileList = dataPath.list(new PatternFilenameFilter("(.*\\.png$)"));
		if(fileList!=null && fileList.length>0){
			Random random = new Random();
			int index = random.nextInt(fileList.length);
			byte[] data = FileUtils.readFileToByteArray(new File(testPath+fileList[index]));
			String result= recognize(data,fileList[index].split("\\.")[0]);
			if(StringUtils.isBlank(result)){
				 new RecognizeResultVO("404", "fail");
			}
			CaptchaVO vo = new CaptchaVO("0","succ");
			vo.setName(fileList[index].split("\\.")[0]);
			vo.setData(Base64.encodeBase64String(data));
			vo.setText(result);
			return vo;
		}else{
			throw new Exception("test data directory  is empty.");
		}
	}
	
	public String recognize(byte[] imgData,String fileName) throws Exception {
		if(isDebug){
			if(StringUtils.isBlank(fileName)){
				throw new Exception("filename param is null for recognize.");
			}
		}
		InputStream imgStream = new ByteArrayInputStream(imgData);
		
		try {
			BufferedImage bufferedImage = ImageIO.read(imgStream);
			int height = bufferedImage.getHeight();
			int width = bufferedImage.getWidth();
			int _threshold =2;
			int[] horizHistogram = new int[height];
			int[] vertiHistogram =new int[width];
			
			//二值化，去噪
			for (int y = 0; y < height; y++) {
				for (int x = 0; x < width; x++) {
					if(x==0 || x== width-1 || y==0 || y== height-1){
						bufferedImage.setRGB(x, y, Color.WHITE.getRGB());
						continue;
					}
					int rgb = bufferedImage.getRGB(x, y);
					if (isBlack(rgb)) {
						int nCount = 0;
						for (int m = y - 1; m <= y + 1; m++){
							if (m < 0 || m >= height ) {
								continue;
							}
							for (int n = x - 1; n <= x + 1; n++) {
								if (n >= width || n < 0) {
									continue;
								}
								if (isBlack(bufferedImage.getRGB(n, m))){
									nCount++;
								}
							}
						}
						if(isDebug) logger.info(nCount+"");
						if (nCount <= _threshold){
							bufferedImage.setRGB(x, y, -1);
						}else{
							vertiHistogram[x]++;
							horizHistogram[y]++;
						}
					} else {
						if(isDebug) logger.info(" ");
						bufferedImage.setRGB(x, y, -1);
					}
				}
				if(isDebug) logger.info("");
			}
			List<SingleCharImageBuffer> singleList = split(vertiHistogram,horizHistogram,bufferedImage);
//			ImageIO.write(bufferedImage, "png", new File("D:/verify_code/target/"+fileName+"_gray_.png"));
			StringBuffer sb = new StringBuffer();
			
			ITesseract _singleTesseract = new Tesseract1();
			_singleTesseract.setDatapath(tessDataPath);
			_singleTesseract.setLanguage("bt");
			_singleTesseract.setPageSegMode(TessPageSegMode.PSM_SINGLE_CHAR);
			
			singleList.forEach(single->{
				try {
					
					int dx1=single.getDx1()-2;
					int dy1=single.getDy1()-2;
					int _weight=single.getDx2()-single.getDx1()+4;
					int _height=single.getDy2()-single.getDy1()+4;
					String chartVC = _singleTesseract.doOCR(bufferedImage, new Rectangle(dx1,dy1,_weight,_height));
					if(isDebug){
						logger.info("dx1:"+single.getDx1()+"dx2:"+single.getDx2()+"dy1:"+single.getDy1()+"dy2:"+single.getDy2());
						ImageIO.write(bufferedImage.getSubimage(dx1,dy1,_weight,_height), "png", new FileOutputStream(new File(testPath+fileName+"_"+generateString(10)+".png")));
					}
					if (StringUtils.isNotBlank(chartVC)){
						chartVC = chartVC.replaceAll("\\s*|\t|\r|\n", "");
						if("0".equalsIgnoreCase(chartVC)){
							chartVC = "O";
						}else if(!"j".equalsIgnoreCase(chartVC) && !"g".equalsIgnoreCase(chartVC) && !"b".equalsIgnoreCase(chartVC)  && !"d".equalsIgnoreCase(chartVC)
								&& !"f".equalsIgnoreCase(chartVC)  && !"k".equalsIgnoreCase(chartVC) & !"y".equalsIgnoreCase(chartVC)  && !"h".equalsIgnoreCase(chartVC)
								& !"q".equalsIgnoreCase(chartVC)){
							if(_height-4<=13){
								chartVC = chartVC.toLowerCase();
							}else{
								chartVC = chartVC.toUpperCase();
							}
							
						}
						sb.append(chartVC);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
			if(isDebug){
				ImageIO.write(bufferedImage, "png", new File(testPath+fileName+"_gray_"+sb.toString()+".png"));
				drawHistogram(vertiHistogram,width,height,1,fileName);
				drawHistogram(horizHistogram,width,height,2,fileName);
			}
			return sb.toString(); 
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 确定字符上下边界
	 * @param bufferedImage
	 * @param dx1
	 * @param dy1
	 * @param dx2
	 * @param dy2
	 * @return
	 */
	public SingleCharImageBuffer border(BufferedImage bufferedImage,SingleCharImageBuffer charBuffer){
		
		for(int y=charBuffer.getDy1();y<charBuffer.getDy2();y++){
			int pixelCounter=0;
			int _x=-1;
			for(int x=charBuffer.getDx1();x<charBuffer.getDx2();x++){
				if(pixelCounter>=2) break;
				if(isBlack(bufferedImage.getRGB(x, y))) {
					pixelCounter++;
					_x = x;
				}
			}
			if(pixelCounter==1){
				bufferedImage.setRGB(_x, y, -1);
			}else if(pixelCounter>1){
				charBuffer.setDy1(y);
				break;
			}
		}
		
		for(int y=charBuffer.getDy2();y>charBuffer.getDy1();y--){
			int pixelCounter=0;
			int _x=-1;
			for(int x=charBuffer.getDx1();x<charBuffer.getDx2();x++){
				if(pixelCounter>=2) break; 
				if(isBlack(bufferedImage.getRGB(x, y))) {
					pixelCounter++;
					_x = x;
				}
			}
			if(pixelCounter==1){
				bufferedImage.setRGB(_x, y, -1);
			}else if(pixelCounter>1){
				charBuffer.setDy2(y);
				break;
			}
		}
		return charBuffer;
	}
	
	/**
	 * 分割字符
	 */
	public List<SingleCharImageBuffer> split(int[] vertiHistogram,int[] horizHistogram,BufferedImage bufferedImage){
		int splitThreshold = 5;
		int dx1=-1;
		int dx2=-1;
		int dy1=-1;
		int dy2=-1;
		List<SingleCharImageBuffer> singleList = Lists.newArrayList();
		for(int y=0;y<horizHistogram.length;y++){
			if(horizHistogram[y]>0){
				if(dy1==-1 ) {
					dy1 = y;
				}
				dy2=y;
			}
		}
		for(int x=0;x<vertiHistogram.length;x++){
			if(vertiHistogram[x]>0){
				if (dx1 == -1) {
					dx1 = x;
					dx2 = x;
				} else {
					dx2++;
				}
				if (vertiHistogram[x] == 1) {
					for (int y = dy1; y < dy2; y++) {
						if (isBlack(bufferedImage.getRGB(x, y))){
//							bufferedImage.setRGB(x, y, -1);
							break;
						}
					}
				}
			}else{
				if(dx1>-1){
					if(dx2-dx1>=splitThreshold){
						SingleCharImageBuffer charBuffer =  new SingleCharImageBuffer(1,dx1,dx2,dy1,dy2,0);
						
						border(bufferedImage,charBuffer);
						singleList.add(charBuffer);
					}
					dx1 =-1;
					dx2 =-1;
				}
			}
		}
		return singleList;
	}
	
	/**
	 * 绘制直方图
	 * 1--垂直直方图 2--水平直方 3--垂直+水平
	 * @param data
	 * @param weight
	 * @param height
	 * @param type
	 */
	public void drawHistogram(int[] data,int weight,int height,int type,String fileName){
		BufferedImage buffer = new BufferedImage(weight,height,BufferedImage.TYPE_BYTE_BINARY);
		Graphics2D g2 = (Graphics2D) buffer.getGraphics();
		g2.setBackground(Color.WHITE);
		g2.clearRect(0, 0, buffer.getWidth(), buffer.getHeight());
		String file = "D:/verify_code/target/";
		switch(type){
		case 1:
			for(int x=0;x<data.length;x++){
				if(data[x]>0){
					for(int y=0;y<data[x];y++){
						buffer.setRGB(x, y, Color.BLACK.getRGB());
					}
				}
			}
			file += fileName+"_vg.png";
			break;
		case 2:
			for(int y=0;y<data.length;y++){
				if(data[y]>0){
					for(int x=0;x<data[y];x++){
						buffer.setRGB(x, y, Color.BLACK.getRGB());
					}
				}
			}
			file += fileName+"_hg.png";
			break;
		case 3:
			file += fileName+"_hvg.png";
			break;
		}
		try {
			ImageIO.write(buffer, "png", new File(file));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public boolean isBlack(int colorInt) {
		Color color = new Color(colorInt);
		if (color.getRed() + color.getGreen() + color.getBlue() <= 300) {
			return true;
		}
		return false;
	}
	
}
